
<!-- タブバー -->
<app_tabbar>
    <ul class="nav nav-tabs" ref="app_tabbar" role="tablist">
        <li each="{tab, i in tabs}" class="nav-item">
            
            <!-- onClickProxy を介することで，どのタブが押されたかを識別する -->
            <a href={"#" + tab.id} 
                onmousedown={tab.onClickProxy} 
                class="{tab.active} nav-link" 
                data-toggle="tab"
            >
                <!-- 
                    &nbsp はタブ名の横のスペース，
                    class="close" は bootstrap で定義された閉じるボタン，
                    &times; は HTML で定義された特殊文字のバツ記号
                -->
                {tab.tabName} &nbsp;

                <!-- onCloseProxy を介することで，どのタブが押されたかを識別する -->
                <button type="button" class="{tab.active} close" onmousedown={tab.onCloseProxy}>
                    &times;
                </button>
            </a>
        </li>
    </ul>

    <script>
        /* globals dispatcher store ACTION CHANGE */
        let self = this;
        self.tabs = [];

        // クリック
        self.onClick = function(e, tabID){
            if (e.buttons & 1) {    // 左クリック - タブを移動
                dispatcher.trigger(ACTION.TAB_ACTIVATE, tabID);
            }
            else if (e.buttons & 2) {   // 右クリック - ポップアップメニュー表示
                let menu = require("./menu.js");
                menu.popupTabMenu(store, dispatcher, tabID);
            }
            else if (e.buttons & 4) {   // 中クリック - 閉じる
                dispatcher.trigger(ACTION.TAB_CLOSE, tabID);
            }

            // <a> にもイベントが行くので，それをブロック
            e.stopPropagation();
            e.preventDefault();
        };

        // タブの閉じるボタンのクリック
        self.onClose = function(e, tabID){
            dispatcher.trigger(ACTION.TAB_CLOSE, tabID);

            // <a> にもイベントが行くので，それをブロック
            e.preventDefault();
            e.stopPropagation();
        };

        // タブの追加/削除が行われた時
        dispatcher.on(CHANGE.TAB_UPDATE, function(){
            // 配列に変換
            self.tabs = [];
            for (let id in store.tabs) {
                let tab = {
                    id: id,
                    fileName: store.tabs[id].fileName,

                    // ファイル名部分を取り出して表示
                    tabName: store.tabs[id].fileName.match(/[^/\\]+$/)[0],

                    // クリックのハンドラを ID ごと埋め込む
                    onClickProxy: function(e){
                        self.onClick(e, id);
                    },
                    onCloseProxy: function(e){
                        self.onClose(e, id);
                    },

                    // アクティブタブかどうか
                    active: (store.activeTabID == id) ? "active" : ""
                };
                self.tabs.push(tab);
            }
            self.update();
        });

        // 高さを取得
        self.getHeight = function(){
            return self.refs.app_tabbar.offsetHeight;
        };
    </script>
</app_tabbar>


<!-- 可視化されたパイプラインを表示するキャンバス -->
<app_pipeline_canvas class="app_pipeline_canvas">
    <!-- relative で囲ってやらないと，子要素の width: 100% がもっと上のサイズを
    基準としたものになる．app_label_canvas 自体が上位で absolute 指定される
    ので，div で一回囲う -->
    <div class="app_pipeline_canvas_container">
        <canvas ref="canvas"></canvas>
        <pre ref="tool_tip" class="tool_tip">{toolTipText}</pre>
    </div>
    
    <!--position を absolute にすることで，同じ位置に重ねることができる 
        見た目に関しては，CSS の方で定義
    -->
    <style>
        app_pipeline_canvas.app_pipeline_canvas{
            /* カスタムタグは デフォルトで inline なので block を指定してやる．
            そうしないと，width が効かない */
            display: block;     
            /* 親要素のサイズぴったりに張り付くようにする */
            width: 100%; height: 100%;
        }
        .app_pipeline_canvas_container {
            position: relative; 
            width: 100%; height: 100%;
        }

        canvas{
            width: 100%;
            height: 100%;
            position: absolute;
        }
        .tool_tip {         /* ラベルのツールチップ */
            position: absolute;
            z-index: 100;
        }
    </style>

    <script>
        /* globals dispatcher store ACTION CHANGE */
        let self = this;
        self.oldWidth = -1;
        self.oldHeight = -1;
        self.id = self.opts.id; // タブ ID は上位から ID 属性として与える
        self.valid = false;

        self.mouseOffset = [0, 0];  // 最後のマウス座標
        self.mouseIn = false;   // マウスがキャンバス上にいるかどうか

        // マウスのドラッグ
        self.inDrag = false;  // マウスでドラッグ中か
        self.prevMousePoint = [0, 0]; // 前回のマウスポインタの位置

        // マウス操作
        self.onMouseWheel = function(e){
            if (e.ctrlKey) {    // 拡大縮小
                dispatcher.trigger(ACTION.KONATA_ZOOM, e.deltaY > 0 ? 1 : -1, e.offsetX, e.offsetY);
            } 
            else {  // スクロール
                dispatcher.trigger(ACTION.KONATA_MOVE_WHEEL_VERTICAL, e.deltaY > 0 ? 1 : -1, true);
            }
        };

        // ダブルクリック
        self.onMouseDoubleClick = function(e){
            // シフトキー時は縮小
            dispatcher.trigger(ACTION.KONATA_ZOOM, e.shiftKey ? 1 : -1, e.offsetX, e.offsetY);
        };

        // マウスクリック
        self.onMouseDown = function(e){
            // 左クリック
            if (e.buttons & 1) {
                self.inDrag = true;
                self.prevMousePoint = [e.clientX, e.clientY];
                dispatcher.trigger(ACTION.KONATA_EMPHASIZE_IN_TRANSPARENT, store.activeTab.id, true);
            }
            // 右クリック
            if (e.buttons & 2) {
                let menu = require("./menu.js");
                menu.popupPipelineMenu(store, dispatcher, [e.offsetX, e.offsetY]);
            }

            // クリック時に他所にフォーカスが奪われるのを防ぐ
            e.preventDefault();
        };        

        // マウスのボタンを離した時
        self.onMouseUp = function(e){
            if (!(e.buttons & 1)) { // 離したので 0 になる
                self.inDrag = false;
                dispatcher.trigger(ACTION.KONATA_EMPHASIZE_IN_TRANSPARENT, store.activeTab.id, false);
            }
        };

        // マウス移動
        self.onMouseMove = function(e){
            if (!self.inDrag){
                return;
            }

            let diff = [
                self.prevMousePoint[0] - e.clientX,
                self.prevMousePoint[1] - e.clientY
            ];
            self.prevMousePoint = [e.clientX, e.clientY];
            dispatcher.trigger(ACTION.KONATA_MOVE_PIXEL_DIFF, diff);
        };

        // ツールチップの更新
        self.updateToolTip = function(){
            let tip = self.refs.tool_tip.style;
            if (self.mouseIn) {
                tip.visibility = "visible";
                tip.left = self.mouseOffset[0] + "px";
                tip.top = self.mouseOffset[1] + 20 + "px";

                let renderer = store.tabs[self.id].renderer;
                let text = renderer.getPipelineToolTipText(self.mouseOffset[0], self.mouseOffset[1]);
                if (!text) {
                    tip.visibility = "hidden";
                }
                self.toolTipText = text;//.split("\n");
            }
            else {
                tip.visibility = "hidden";
            }
            self.update();
        };

        // ツールチップ用の canvas ローカルなマウス移動
        self.onLocalMouseMove = function(e){
            self.mouseOffset = [e.offsetX, e.offsetY];
            self.mouseIn = true;
            self.updateToolTip();
        };
        self.onLocalMouseLeave = function(){
            self.mouseIn = false;
            self.updateToolTip();
        };

        // ペーンの内容の更新
        self.onPaneContentUpdate = function(){
            let canvas = self.refs.canvas;
            store.tabs[self.id].renderer.drawPipeline(canvas);
            self.updateToolTip();
        };

        // canvas.clientWidth が変わった後の情報が欲しいので，サイズ適用後に呼ぶ
        self.on("updated", function() {
            let canvas = self.refs.canvas;
            let width = canvas.clientWidth;
            let height = canvas.clientHeight;

            if (self.oldWidth != width || self.oldHeight != height){
                // サイズを変更すると canvas の中身が破棄されるので，
                // 本当に変わったときだけ反映する
                self.oldWidth = width;
                self.oldHeight = height;

                // High DPI 対策
                let context = canvas.getContext("2d");
                let devicePixelRatio = window.devicePixelRatio || 1;
                let backingStoreRatio = context.backingStorePixelRatio || 1;
                
                if (devicePixelRatio !== backingStoreRatio) {
                    let ratio = devicePixelRatio / backingStoreRatio;
                    canvas.width = width * ratio;
                    canvas.height = height * ratio;
                    context.scale(ratio, ratio);
                }
                else{
                    canvas.width = width;
                    canvas.height = height;
                    context.scale(1, 1);
                }
            }
        });

        // エントリポイント
        // マウント時に初期化を行う
        self.on("mount", function(){

            let canvas = self.refs.canvas;

            canvas.ondblclick = self.onMouseDoubleClick;
            canvas.onmousewheel = self.onMouseWheel;
            canvas.onmousedown = self.onMouseDown;
            canvas.onmousemove = self.onLocalMouseMove;
            canvas.onmouseleave = self.onLocalMouseLeave;

            // 外でも捉えられるように window に
            window.addEventListener("mousemove", self.onMouseMove);
            window.addEventListener("mouseup", self.onMouseUp);
            self.valid = true;
            dispatcher.on(CHANGE.PANE_CONTENT_UPDATE, self.onPaneContentUpdate);
            
            let tip = self.refs.tool_tip.style;
            tip.visibility = "hidden";
        });

        self.on("unmount", function(){
            window.removeEventListener("mousemove", self.onMouseMove);
            window.removeEventListener("mouseup", self.onMouseUp);
            self.valid = false;
            dispatcher.off(CHANGE.PANE_CONTENT_UPDATE, self.onPaneContentUpdate);
        });

    </script>
</app_pipeline_canvas>


<!-- 逆アセンブル結果を表示するキャンバス -->
<app_label_canvas class="app_label_canvas">

    <div class="app_label_canvas_container">
        <!-- 実際にアセンブリを表示する部分 -->
        <canvas ref="canvas"></canvas>

        <!-- マウスオーバージのヒント -->
        <pre ref="tool_tip" class="tool_tip">{toolTipText}</pre>

        <!-- 検索結果 -->
        <div ref="find_result_tip" class="find_result_tip">
            <div ref="find_result_tip_content" class="find_result_tip_content">
            </div>
            <button ref="find_result_tip_close" type="button" class="find_result_tip_close">
                &times;
            </button>
        </div>
    </div>
    
    <style>
        app_label_canvas.app_label_canvas{
            /* 親要素のサイズぴったりに張り付くようにする */
            width: 100%; height: 100%;
            /* カスタムタグは デフォルトで inline なので block を指定してやる．
            そうしないと，width が効かない */
            display: block;     
        }

        .app_label_canvas_container {
            /* relative で囲ってやらないと，子要素の width: 100% がもっと上のサイズを
            基準としたものになる．app_label_canvas 自体が上位で absolute 指定される
            ので，div で一回囲う */
            position: relative; 
            width: 100%; height: 100%;
        }

        canvas{
            width: 100%;
            height: 100%;
            position: absolute;
        }

        .tool_tip{
            position: absolute;
            z-index: 100;
        }
        .find_result_tip {
            position: absolute;
            z-index: 100;
        }
        .find_result_tip_content {
            position: relative;
            display: inline-block;  /* 横に閉じるボタンを並べる */
        }
        .find_result_tip_close {
            position: relative;
            display: inline-block;
        }
    </style>

    <script>
        /* globals dispatcher store ACTION CHANGE */

        let self = this;
        self.oldWidth = -1;
        self.oldHeight = -1;

        self.mouseOffset = [0, 0];  // 最後のマウス座標
        self.mouseIn = false;   // マウスがキャンバス上にいるかどうか

        self.id = self.opts.id; // タブ ID は上位から ID 属性として与える

        // 初期化
        // タグのマウント時に呼ばれるようにする
        self.init = function(){
            let canvas = self.refs.canvas;
            canvas.onmousewheel = self.onMouseWheel;
            canvas.onmousedown = self.onMouseDown;
            canvas.onmousemove = self.onMouseMove;
            canvas.onmouseleave = self.onMouseLeave;
            
            dispatcher.on(CHANGE.PANE_CONTENT_UPDATE, self.onPaneContentUpdate);

            let tip = self.refs.tool_tip.style;
            tip.visibility = "hidden";

            let find_result_tip = self.refs.find_result_tip.style;
            find_result_tip.visibility = "hidden";

            self.refs.find_result_tip_close.onmousedown = self.onCloseFindResult;
        };

        // マウス操作
        self.onMouseWheel = function(e){
            if (e.ctrlKey) {    // 拡大縮小
                dispatcher.trigger(ACTION.KONATA_ZOOM, e.deltaY > 0 ? 1 : -1, e.offsetX, e.offsetY);
            } 
            else {  // スクロール
                dispatcher.trigger(ACTION.KONATA_MOVE_WHEEL_VERTICAL, e.deltaY > 0 ? 1 : -1, true);
            }
        };

        // マウスクリック
        self.onMouseDown = function(e){
            // 左クリック
            if (e.buttons & 1) {
                // クリック点に対応した命令を取得し，そこへ移動
                dispatcher.trigger(ACTION.KONATA_MOVE_LABEL_CLICK, e.offsetY);
            }
            // 右クリック
            /*
            if (e.buttons & 2) {
            }
            */

            // クリック時に他所にフォーカスが奪われるのを防ぐ
            e.preventDefault();
        };

        // ツールチップの更新
        self.updateToolTip = function(){

            // 検索結果
            let findCtx = store.tabs[self.id].findContext;
            let find_result_tip = self.refs.find_result_tip.style;
            if (findCtx.found && findCtx.visibility) {
                find_result_tip.visibility = "visible";
                find_result_tip.left = "0px";

                let renderer = store.tabs[self.id].renderer;
                find_result_tip.top = Math.floor(renderer.getPixelPosYFromOp(findCtx.op)) + renderer.opH + "px";

                // パターンにヒットした行を表示する
                self.findResultTipText = "";

                // 非表示になっている場合はメッセージを追加
                if (store.activeTab.hideFlushedOps && findCtx.flushed) {
                    self.findResultTipText += "A found op is not shown because it is flushed.<br>";
                }

                let re = new RegExp(findCtx.targetPattern, "g");
                for (let line of findCtx.foundStr.split("\n")) {
                    let m = re.exec(line);
                    if (m || self.findResultTipText == "") {  // 最初の行はつけておく
                        self.findResultTipText += line.replace(re, "<span style=\"color:cyan\">$&</span>") + "<br>";
                    }
                }

                self.refs.find_result_tip_content.innerHTML = self.findResultTipText;
            }
            else{
                find_result_tip.visibility = "hidden";
            }

            // マウスオーバーした領域のラベルの表示
            let tip = self.refs.tool_tip.style;
            if (self.mouseIn) {
                tip.visibility = "visible";
                tip.left = self.mouseOffset[0] + "px";
                tip.top = self.mouseOffset[1] + 20 + "px";

                let text = store.tabs[self.id].renderer.getLabelToolTipText(self.mouseOffset[1]);
                if (!text) {
                    tip.visibility = "hidden";
                }
                self.toolTipText = text;//.split("\n");
            }
            else {
                tip.visibility = "hidden";
            }
            self.update();
        };

        //
        self.onMouseMove = function(e){
            self.mouseOffset = [e.offsetX, e.offsetY];
            self.mouseIn = true;
            self.updateToolTip();
        };
        self.onMouseLeave = function(){
            self.mouseIn = false;
            self.updateToolTip();
        };

        self.onCloseFindResult = function(){
            dispatcher.trigger(ACTION.KONATA_FIND_HIDE_RESULT);
        };

        // ペーンの内容の更新
        self.onPaneContentUpdate = function(){
            let canvas = self.refs.canvas;
            store.tabs[self.id].renderer.drawLabel(canvas);
            self.updateToolTip();
        };

        // canvas.clientWidth が変わった後の情報が欲しいので，サイズ適用後に呼ぶ
        self.on("updated", function() {
            let canvas = self.refs.canvas;
            let width = canvas.clientWidth;
            let height = canvas.clientHeight;

            if (self.oldWidth != width || self.oldHeight != height){
                // サイズを変更すると canvas の中身が破棄されるので，
                // 本当に変わったときだけ反映する
                self.oldWidth = width;
                self.oldHeight = height;

                // High DPI 対策
                let context = canvas.getContext("2d");
                let devicePixelRatio = window.devicePixelRatio || 1;
                let backingStoreRatio = context.backingStorePixelRatio || 1;
                
                if (devicePixelRatio !== backingStoreRatio) {
                    let ratio = devicePixelRatio / backingStoreRatio;
                    canvas.width = width * ratio;
                    canvas.height = height * ratio;
                    context.scale(ratio, ratio);
                }
                else{
                    canvas.width = width;
                    canvas.height = height;
                    context.scale(1, 1);
                }
            }
        });

        // エントリポイント
        // マウント時に初期化を行う
        self.on("mount", self.init);

        self.on("unmount", function(){
            dispatcher.off(CHANGE.PANE_CONTENT_UPDATE, self.onPaneContentUpdate);
        });

    </script>
</app_label_canvas>

<!-- label + splitter + stages -->
<app_sheet class="app_sheet">
    <div class="sheet_container" ref="sheet_container">
        <splitter_window ref="splitter_window">
            <yield to="first_pane">
                <!-- 子供側のコンテキストで評価されるので，parent がいる-->
                <app_label_canvas id={parent.id} ref="label"></app_label_canvas>
            </yield>
            <yield to="splitter">
                <div class="window_splitter"></div>
            </yield>
            <yield to="second_pane">
                <app_pipeline_canvas id={parent.id} ref="pipeline"></app_pipeline_canvas>
            </yield>
        </splitter_window>
    </div>

    <style>
        /*
        app_sheet のスタイルを有効して広げると，裏に回ったタブ内部の 
        container へマウスのイベントが不可視の app_sheet に横取りされる．
        したがって，ここでは app_sheet 自体はサイズ０のままにしておく
        app_sheet.app_sheet {
            display: block; position:absolute; width: 100%; height: 100%;
        }
        */
  
        .sheet_container {
            /* sheet_container 同士を同じ位置において z-index を制御するため，位置を絶対指定に．
            absolute を指定すると，親要素の左上を基準とした位置に配置される
            */
            position: absolute; 
            width: 100%;
            height: 100%;
        }
    </style>

    <script>

        /* globals dispatcher store ACTION CHANGE */
        let self = this;
        self.id = self.opts.id; // タブ ID は上位から ID 属性として与える
        self.valid = false;

        self.on("mount", function(){

            // スプリッタ位置が動いたらディスパッチする
            // シンクロスクロール時に，他のタブのスプリッタも同時に動かすため
            let splitter_window = self.refs.splitter_window;
            splitter_window.on("splitter_pos_updated", (splitterPos) => {
                dispatcher.trigger(ACTION.PANE_SPLITTER_MOVE, splitterPos);
            });

            dispatcher.on(CHANGE.PANE_SIZE_UPDATE, () => {
                if (!(self.id in store.tabs)) {
                    console("An unknown tab id is used in onPaneSizeUpdate.");
                    return;
                }
                let tab = store.tabs[self.id];
                splitter_window.trigger("update_pane_size", tab.splitterPos);
            });
            dispatcher.on(CHANGE.TAB_UPDATE, self.onTabUpdate);
            
            self.valid = true;
        });

        self.on("unmount", function(){
            self.valid = false;
            dispatcher.off(CHANGE.PANE_SIZE_UPDATE, self.onPaneSizeUpdate);
            dispatcher.off(CHANGE.TAB_UPDATE, self.onTabUpdate);
        });

        // タブの内容の更新
        // シートの zindex を操作して，アクティブなもののみを見せる
        self.onTabUpdate = function(){
            if (!self.valid) {
                return;
            }
            // これは全てのタブで実行する必要がある
            let splitter_window = self.refs.sheet_container;
            let pipeline = self.refs.splitter_window.refs.pipeline.refs.canvas;

            if (self.id == store.activeTab.id) {
                splitter_window.style.zIndex = "0";   // アクティブなタブを最前面に
                // 透明化
                if (store.activeTab.transparent) {
                    pipeline.style.opacity = store.activeTab.emphasize_in_transparent ? "0.8" : "0.5"; // 透明化
                }
                else {
                    pipeline.style.opacity = "1";
                }
            }
            else {
                // 直前に表示されていたタブは優先度をあげておく
                splitter_window.style.zIndex = store.prevTabID == self.id ?  "-1" : "-2";
                pipeline.style.opacity = "1";   // 透明化の有効無効
            }


            self.update();
        };

    </script>
</app_sheet>

<!-- List of sheets -->
<app_sheet_list class="app_sheet_list">
    <app_sheet each="{sheet, i in sheets}" id={sheet.id}>
    </app_sheet>
    <style>
        app_sheet_list.app_sheet_list {
            display: block; position:absolute; width: 100%; height: 100%;
        }
    </style>
    <script>
        /* globals dispatcher store CHANGE */
        let self = this;
        self.width = 0;
        self.height = 0;
        
        dispatcher.on(CHANGE.TAB_UPDATE, function(){
            self.sheets = [];
            for (let id in store.tabs) {
                self.sheets.push(store.tabs[id]);
            }
            self.update();
        });
    </script>
</app_sheet_list>

<!-- コマンドパレット -->
<app_command_palette>

    <div ref="command_palette" class="command_palette">
        <input ref="input" type="text" class="command_palette_input">
        <div 
            class="command_palette_hint_item"
            each="{hint, hintID in hintList}" 
            ref={"hint_" + hintID} 
        >
            <div class="command_palette_hint_item_text">{hint.text}</div>:
            <div class="command_palette_hint_item_command">{hint.cmd}</div>
        </div>
    </div>

    <script>
        /* globals dispatcher ACTION CHANGE store */
        let self = this;
        self.historyIndex = -1;
        self.hintList = {
            0: {"text": "Jump to #line",                "cmd": "j  <#line>"},
            1: {"text": "Jump to an op with rid",       "cmd": "jr <rid>"},
            2: {"text": "Find a string ('F3' key finds next)",  "cmd": "f  <string>"},
            3: {"text": "Load a file",                  "cmd": "l  <file name>"},
        };
        self.init = function(){
            // フォーカスが外れたら消す
            let input = self.refs.input;
            input.onblur = function(){
                dispatcher.trigger(ACTION.COMMAND_PALETTE_CLOSE);
            };
        };
        // エントリポイント
        self.on("mount", self.init);

        // キーボードハンドラ
        // ESC が押されたら消す
        self.onKeyDown = function(e){
            // キーボード
            let key = e.key;
            if (key == "Escape") {
                dispatcher.trigger(ACTION.COMMAND_PALETTE_CLOSE);
            }
            else if (key == "Enter") {
                let input = self.refs.input;
                dispatcher.trigger(ACTION.COMMAND_PALETTE_EXECUTE, input.value);
                dispatcher.trigger(ACTION.COMMAND_PALETTE_CLOSE);
            }
            else if (key == "ArrowUp" || key == "ArrowDown") {
                let input = self.refs.input;
                self.historyIndex += key == "ArrowUp" ? 1 : -1;
                self.historyIndex = Math.min(store.config.commandHistory.length - 1, Math.max(-1, self.historyIndex));
                let cmd = self.historyIndex == -1 ? "" : store.config.commandHistory[self.historyIndex];
                input.value = cmd;
                input.selectionStart = cmd.length;
                input.selectionEnd = cmd.length;
                e.preventDefault(); // ArrowUp/ArrowDown may change a caret position.
            }
        };

        // オープン
        dispatcher.on(CHANGE.COMMAND_PALETTE_OPEN,
            function(command){
                let cp = self.refs.command_palette.style;
                cp.visibility = "visible";
                // 画面外からスライドイン
                // フォーカスを合わせるためには即座に可視化されてくれないといけないので
                // visibility は 0 に
                //cp.transition = "visibility 0s, top 0.05s ease-out";
                //cp.top = "0px";

                self.historyIndex = -1;

                // フォーカスを合わせる
                let input = self.refs.input;
                input.value = command;
                input.focus();

                // キーボードハンドラを登録
                window.addEventListener("keydown", self.onKeyDown);
            }
        );

        // クローズ
        dispatcher.on(CHANGE.COMMAND_PALETTE_CLOSE,
            function(){
                let cp = self.refs.command_palette.style;
                cp.visibility = "hidden";
                // 画面外へスライドアウト
                // cp.transition = "visibility 0.1s, top 0.1s ease-out";
                // cp.top = "-" + self.refs.command_palette.offsetHeight + "px";

                // キーボードハンドラを解除
                window.removeEventListener("keydown", self.onKeyDown);

                // 中身をクリア
                let input = self.refs.input;
                input.value = "";
            }
        );

    </script>
</app_command_palette>

<!-- 読み込みのプレグレスバー -->
<app_progress_bar>

    <div class="progress_bar_container">
        <div 
            each="{bar, barID in barList}" 
            class={"progress_bar_" + ((store.activeTabID == bar.tabID) ? ("active " + bar.barType) : "background")}
            ref={"progress_bar_" + barID} 
        >
        </div>
    </div>

    <script>
        /* globals dispatcher store ACTION CHANGE */
        let self = this;
        self.barList = {};
        self.store = store;
        self.init = function(){
        };
        // エントリポイント
        self.on("mount", self.init);

        self.makeBarID = function(tabID, barType){
            return tabID + "_" + barType;
        };

        // タブの移動
        dispatcher.on(ACTION.TAB_ACTIVATE,
            () => {
                self.update();  // アクティブなプログレスバーのスタイルを更新
            }
        );
        
        // オープン
        dispatcher.on(CHANGE.PROGRESS_BAR_START,
            function(tabID, barType="default"){
                //console.log("Start progress bar: ", tabID);
                let barID = self.makeBarID(tabID, barType);
                self.barList[barID] = {
                    tabID: tabID,
                    barType: barType,
                    ratio: 0
                };
                self.update();
                let bar = self.refs["progress_bar_" + barID].style;
                bar.width = "0%";
            }
        );

        // プログレスバー
        dispatcher.on(CHANGE.PROGRESS_BAR_UPDATE,
            function(ratio, tabID, barType="default"){
                let barID = self.makeBarID(tabID, barType);
                if (!(barID in self.barList)) {
                    // タブをロード中に途中で閉じると，PROGRESS_BAR_FINISH が来た後にも
                    // バッファ内のロードがしばらく継続されるため，更新ハンドラが
                    // よばれることがある
                    return; 
                }
                self.barList[barID].ratio = ratio;
                let bar = self.refs["progress_bar_" + barID].style;
                //bar.width = Math.floor(100 * ratio) + "%";
                bar.width = (100 * ratio) + "%";
                self.update();
            }
        );
        
        dispatcher.on(CHANGE.PROGRESS_BAR_FINISH,
            function(tabID, barType="default"){
                let barID = self.makeBarID(tabID, barType);
                if (!(barID in self.barList)) {
                    return;
                }
                //console.log("Close progress bar: ", tabID);
                delete self.barList[barID];
                self.update();
            }
        );

    </script>
</app_progress_bar>



<!-- Application -->
<app>
    <!-- 各要素の配置について
    * app_tabbar 内の ul は CSS で位置の指定をしていないため，最上部に表示
    * app_sheet_list 内の各シートは sheet_container で position: absolute; 
      が指定されているため，タブの直下を基準として全て重なる
    * app_dialogs 内の各ダイアログは何もしなければタブ直下だが，bootstrap が画面全体に
      無理矢理かぶせていると思われる
    * コマンドパレットも position: absolute; であるため，タブ直下で重なる
    -->


    <!-- tab bar -->
    <app_tabbar></app_tabbar>
    
    <!-- client -->
    <app_sheet_list ref="app_sheet_list"></app_sheet_list>

    <!-- 色々なダイアログ -->
    <app_dialogs></app_dialogs>

    <!-- コマンドパレット -->
    <app_command_palette></app_command_palette>

    <!-- 読み込みのプレグレスバー -->
    <app_progress_bar></app_progress_bar>

    <script>
        let self = this;

        /* global dispatcher ACTION CHANGE store */
        let rc = dispatcher;

        self.remote = require("electron").remote;

        // 新規タブのオープン
        dispatcher.on(CHANGE.TAB_OPEN,
            function(tab){
                let path = tab.fileName;
                if (!path) {
                    return;
                }
                //console.log(path);
            }
        );

        // リサイズハンドラ
        self.onResize = function(){
            // シートのサイズを計算
            let win = self.remote.getCurrentWindow();
            let bounds = win.getBounds();   // {x, y, width, height}
            rc.trigger(ACTION.SHEET_RESIZE, bounds);
        };

        // Window move handler
        self.onMove = function(){
            self.onResize();    // Redirect to onResize
        };

        // フォーカスハンドラ
        self.onFocus = function(){
            rc.trigger(ACTION.FILE_CHECK_RELOAD);
        };

        // 開発者ツールの表示切り替え
        dispatcher.on(CHANGE.SHEET_UPDATE_DEV_TOOL, function(show){
            let win = self.remote.getCurrentWindow();
            if (show) {
                win.openDevTools();
            }
            else {
                win.closeDevTools();
            }
        });

        // CSS の更新
        self.onCSS_Update = function(){
            let themeCSS = store.config.THEME_CSS_LIST[store.config.theme];
            let css = document.getElementById("konata_theme_css");
            css.href = themeCSS;
        };
        dispatcher.on(CHANGE.WINDOW_CSS_UPDATE, self.onCSS_Update);

        // 初期化
        // マウント時に呼ばれる
        self.init = function(){
            // Get the current window
            let electronWindow = self.remote.getCurrentWindow();

            // Must set window bounds before resize/move handlers are set
            // to avoid overwriting config.windowBounds
            electronWindow.setBounds(store.config.windowBounds);

            // CSS の設定
            self.onCSS_Update();

            // メニューを追加
            let menu = require("./menu.js");
            menu.installMainMenu(store, dispatcher);

            // フォーカス
            window.onfocus = self.onFocus;

            // リサイズハンドラ
            window.addEventListener("resize", self.onResize, false);
            self.onResize();    // サイズを適用しておくために1回呼んでおく

            // Window move handler
            electronWindow.on("move", self.onMove);

            // ドラッグアンドドロップ
            document.ondragover = function(e) {
                e.preventDefault(); // イベントの伝搬を止めて、アプリケーションのHTMLとファイルが差し替わらないようにする
                return false;
            };

            document.ondrop = function(e) {
                e.preventDefault(); // イベントの伝搬を止めて、アプリケーションのHTMLとファイルが差し替わらないようにする
                for (let file of e.dataTransfer.files) {
                    rc.trigger(ACTION.FILE_OPEN, file.path);
                }
            };

            // キーボード
            document.onkeydown = function(e) {
                let key = e.key;

                if (!store.isAnyDialogOpened()) {
                    // These keys are valid when a command palette is not opened.
                    if (key == "ArrowUp") {
                        if (e.ctrlKey) {
                            let w = self.refs.app_sheet_list.root.offsetWidth - store.activeTab.splitterPos;
                            let h = self.refs.app_sheet_list.root.offsetHeight;
                            dispatcher.trigger(ACTION.KONATA_ZOOM, -1, w / 2, h / 2);
                        }
                        else{
                            // shift 時は水平方向補正なし
                            dispatcher.trigger(ACTION.KONATA_MOVE_WHEEL_VERTICAL, -1, !e.shiftKey);
                        }
                    }
                    else if (key == "ArrowDown") {
                        if (e.ctrlKey) {
                            let w = self.refs.app_sheet_list.root.offsetWidth - store.activeTab.splitterPos;
                            let h = self.refs.app_sheet_list.root.offsetHeight;
                            dispatcher.trigger(ACTION.KONATA_ZOOM, 1, w / 2, h / 2);
                        }
                        else{
                            dispatcher.trigger(ACTION.KONATA_MOVE_WHEEL_VERTICAL, 1, !e.shiftKey);
                        }
                    }
                    else if (key == "PageUp") {
                        dispatcher.trigger(ACTION.KONATA_MOVE_WHEEL_VERTICAL, -10, !e.ctrlKey);
                    }
                    else if (key == "PageDown") {
                        dispatcher.trigger(ACTION.KONATA_MOVE_WHEEL_VERTICAL, 10, !e.ctrlKey);
                    }
                    else if (key == "ArrowLeft") {
                        dispatcher.trigger(ACTION.KONATA_MOVE_WHEEL_HORIZONTAL, -1);
                    }
                    else if (key == "ArrowRight") {
                        dispatcher.trigger(ACTION.KONATA_MOVE_WHEEL_HORIZONTAL, 1);
                    }
                    else if (key == "+") {
                        let w = self.refs.app_sheet_list.root.offsetWidth - store.activeTab.splitterPos;
                        let h = self.refs.app_sheet_list.root.offsetHeight;
                        dispatcher.trigger(ACTION.KONATA_ZOOM, -1, w / 2, h / 2);
                    }
                    else if (key == "-") {
                        let w = self.refs.app_sheet_list.root.offsetWidth - store.activeTab.splitterPos;
                        let h = self.refs.app_sheet_list.root.offsetHeight;
                        dispatcher.trigger(ACTION.KONATA_ZOOM, 1, w / 2, h / 2);
                    }
                    else if (key == "F3") {
                        if (e.shiftKey) {
                            dispatcher.trigger(ACTION.KONATA_FIND_PREV_STRING);
                        }
                        else{
                            dispatcher.trigger(ACTION.KONATA_FIND_NEXT_STRING);
                        }
                    }
                    else if (key == "P" && e.ctrlKey && e.shiftKey) {
                        dispatcher.trigger(ACTION.COMMAND_PALETTE_OPEN, "");
                    }
                    else if (key == "Escape" || key == "Enter") {
                        dispatcher.trigger(ACTION.KONATA_FIND_HIDE_RESULT);
                    }
                }
                
                //console.log(key);
            };

            // Now all initialization is finished and show window.
            electronWindow.show();

            // Notify the app has been initialized
            dispatcher.trigger(ACTION.APP_INITIALIZED);
        };

        // エントリポイント
        self.on("mount", self.init);
    </script>
</app>


